/*
 *  testClassDataDesc.cpp
 *  kangmodb
 *
 *  Created by 강모 김 on 11. 4. 25..
 *  Copyright 2011 강모소프트. All rights reserved.
 *
 */

#include "gtest/gtest.h"

#include "data.h"
#include "testCommon.h"

/** @brief Test stgDataDesc class 
 */
class ClassDataDescTest : public testing::Test {
protected:  // You should make the members protected s.t. they can be
	// accessed from sub-classes.
	
	// virtual void SetUp() will be called before each test is run.  You
	// should define it if you need to initialize the varaibles.
	// Otherwise, this can be skipped.
	virtual void SetUp() {
		dataDesc_ = new stgDataDesc();
		ASSERT_TRUE( dataDesc_->getComittedVersion() == NULL );
		ASSERT_TRUE( dataDesc_->getUncomittedVersion() == NULL );
		ASSERT_TRUE( dataDesc_->isModifyingTransaction( NULL ) );
	}
	
	// virtual void TearDown() will be called after each test is run.
	// You should define it if there is cleanup work to do.  Otherwise,
	// you don't have to provide it.
	//
	virtual void TearDown() {
		delete dataDesc_;
	}
protected:
	stgDataDesc * dataDesc_;
	
	KD_VOID addComittedVersion(const char * dataStr, stgVersion commitVersion)
	{
		KD_TRY
		{
			stgDataVersion * data;
			KD_ASSERT( allocVersion(dataStr, & data ) == 0 );
			data->spID_or_CommitVersion = commitVersion;
			(void) dataDesc_->addComittedVersion( data );
		}
		KD_CATCH
		KD_FINALLY
		KD_END
	}

	KD_VOID addUncomittedVersion(const char * dataStr, stgSavepointId spID)
	{
		KD_TRY
		{
			stgDataVersion * data;
			KD_ASSERT( allocVersion(dataStr, & data ) == 0 );
			data->spID_or_CommitVersion = spID;
			(void) dataDesc_->addUncomittedVersion( data );
		}
		KD_CATCH
		KD_FINALLY
		KD_END
	}
	
};


// TODO : addUncomittedVersion with savepoint id in reverse order. This should hit assertion.
// Test operations related to savapoint 
TEST_F(ClassDataDescTest, DISABLED_Savepoint) {
	ASSERT_TRUE( addUncomittedVersion("v997_sp0", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp0" ) ); 
	ASSERT_TRUE( addUncomittedVersion("v997_sp1", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp1" ) ); 
	ASSERT_TRUE( addUncomittedVersion("v997_sp2", 2 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp2" ) ); 
	ASSERT_TRUE( addUncomittedVersion("v997_sp3", 3 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp3" ) ); 
	
	// Try rollback to savepoint 3
	ASSERT_TRUE( dataDesc_->removeUncomittedVersions(3) == 0 );
	// sp2 should be the latest version
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp2" ) ); 

	// Begin a new savepoint 4, and then add a version.
	ASSERT_TRUE( addUncomittedVersion("v997_sp4", 4 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp4" ) ); 

	// Try rollback to savepoint 1
	ASSERT_TRUE( dataDesc_->removeUncomittedVersions(1) == 0 );

	// sp0 should be the latest version
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp0" ) ); 

	// Begin savepoint 4, 5 adding a new version for each
	ASSERT_TRUE( addUncomittedVersion("v997_sp4", 4 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp4" ) ); 
	ASSERT_TRUE( addUncomittedVersion("v997_sp5", 5 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp5" ) ); 

	// Try rollback to savepoint 0 ( total rollback )
	ASSERT_TRUE( dataDesc_->removeUncomittedVersions(0) == 0 );
	
	// There should be no version at all
	ASSERT_TRUE( dataDesc_->getUncomittedVersion() == NULL ); 
}

// Test adding comitted versions. addComittedVersion interface is not directly called, but called inside of commitChanges interface.  
TEST_F(ClassDataDescTest, DISABLED_AddCommittedVersion) {
	stgDataVersion * v;

	ASSERT_TRUE( addComittedVersion("v997", 997 /* commit version */) == 0 );

	ASSERT_TRUE( addComittedVersion("v998", 998 /* commit version */) == 0 );

	ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v997" ) ); 

	ASSERT_TRUE( dataDesc_->findLatestVersion( 998, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v998" ) ); 
}

// Test removing comitted data versions whose versions are less than a given one. This is used by garbage collector. 
TEST_F(ClassDataDescTest, DISABLED_GarbageCollector) {

	ASSERT_TRUE( addComittedVersion("v997", 997 /* commit version */) == 0 );

	ASSERT_TRUE( addComittedVersion("v998", 998 /* commit version */) == 0 );

	ASSERT_TRUE( addComittedVersion("v999", 999 /* commit version */) == 0 );

	ASSERT_TRUE( addComittedVersion("v1000", 1000 /* commit version */) == 0 );

	ASSERT_TRUE( dataDesc_->removeComittedVersionsLessThan(999) == 0 );

	// Now the committed version list should be "v1000"->"v999"->NULL
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v1000" ) ); 
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion()->nextOldVersion, "v999" ) );
	ASSERT_TRUE( dataDesc_->getComittedVersion()->nextOldVersion->nextOldVersion == NULL );
}

// Test operations related to Removing the latest uncomitted version. 
TEST_F(ClassDataDescTest, DISABLED_removeUncomittedVersion) {
	stgDataVersion * v;
	
	// At first, there is no uncomitted version at all
	ASSERT_TRUE( dataDesc_->removeUncomittedVersion() == NULL );
	
	// Add v997_sp0
	ASSERT_TRUE( addUncomittedVersion("v997_sp0", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp0" ) ); 
	
	// Add v997_sp1
	ASSERT_TRUE( addUncomittedVersion("v997_sp1", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp1" ) ); 
	
	// The latest version removed is v997_sp1
	v = dataDesc_->removeUncomittedVersion();
	ASSERT_TRUE( isDataEqual( v, "v997_sp1" ) ); 
	ASSERT_TRUE( freeVersion( v ) == 0);

	// Add v997_sp1 with a greater size
	ASSERT_TRUE( addUncomittedVersion("v997_sp1_greater_size_1234567890", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997_sp1_greater_size_1234567890" ) ); 

	v = dataDesc_->removeUncomittedVersion();
	ASSERT_TRUE( isDataEqual( v, "v997_sp1_greater_size_1234567890" ) ); 
	ASSERT_TRUE( freeVersion( v ) == 0);

	// The latest version removed is v997_sp0
	v = dataDesc_->removeUncomittedVersion();
	ASSERT_TRUE( isDataEqual( v, "v997_sp0" ) ); 
	ASSERT_TRUE( freeVersion( v ) == 0);
	
	// All versions are removed now. No more versions at all.
	ASSERT_TRUE( dataDesc_->removeUncomittedVersion() == NULL );
}

// Test operations related to finding the latest version including uncomitted changes. 
TEST_F(ClassDataDescTest, DISABLED_findUncomittedVersion) {
	stgDataVersion * v;
	
	// Add v997_sp0
	ASSERT_TRUE( addUncomittedVersion("v997", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v997" ) ); 
	
	// Commit with commit version 997
	ASSERT_TRUE( dataDesc_->commitChanges(997) );

	// Add v998_sp0
	ASSERT_TRUE( addUncomittedVersion("v998_uncomitted", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v998_uncomitted" ) ); 

	ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v997" ) ); 

	ASSERT_TRUE( dataDesc_->findLatestVersion( 998, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v997" ) ); 

	// Specify MAX_DATA_VERSION to read the uncomitted changes. 
	ASSERT_TRUE( dataDesc_->findLatestVersion( MAX_DATA_VERSION, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v998_uncomitted" ) ); 
}


// Test committing changes
TEST_F(ClassDataDescTest, DISABLED_Commit_And_FindVersions ) {
	// Commit version 996 : ( data : v996_sp0 )
	{
		ASSERT_TRUE( addUncomittedVersion("v996_sp0", 0 /* savepoint id */) == 0 );
		ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v996_sp0" ) ); 
		
		ASSERT_TRUE( dataDesc_->commitChanges(996) );
		ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v996_sp0" ) ); 
		
	}

	// Commit version 998 ( data : v998_sp1 )
	{
		ASSERT_TRUE( addUncomittedVersion("v998_sp0", 0 /* savepoint id */) == 0 );
		ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v998_sp0" ) ); 

		ASSERT_TRUE( addUncomittedVersion("v998_sp1", 1 /* savepoint id */) == 0 );
		ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v998_sp1" ) ); 
		
		ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp0" ) ); 
		ASSERT_TRUE( dataDesc_->commitChanges(998) );
		ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v998_sp1" ) ); 
	}

	// Commit version 999 ( data : v999_sp2 )
	{
		ASSERT_TRUE( addUncomittedVersion("v999_sp0", 0 /* savepoint id */) == 0 );
		ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v999_sp0" ) ); 

		ASSERT_TRUE( addUncomittedVersion("v999_sp1", 1 /* savepoint id */) == 0 );
		ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v999_sp1" ) ); 

		ASSERT_TRUE( addUncomittedVersion("v999_sp2", 1 /* savepoint id */) == 0 );
		ASSERT_TRUE( isDataEqual( dataDesc_->getUncomittedVersion(), "v999_sp2" ) ); 
		
		ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v998_sp1" ) ); 
		ASSERT_TRUE( dataDesc_->commitChanges(999) );
		ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v999_sp2" ) ); 
	}
	
	stgDataVersion * v;
	// Find with different view versions
	{
		ASSERT_TRUE( dataDesc_->findLatestVersion( 995, & v) == 0 );
		ASSERT_TRUE( v == NULL ); 
	
		ASSERT_TRUE( dataDesc_->findLatestVersion( 996, & v) == 0 );
		ASSERT_TRUE( isDataEqual( v, "v996_sp0" ) ); 

		ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
		ASSERT_TRUE( isDataEqual( v, "v996_sp0" ) ); 

		ASSERT_TRUE( dataDesc_->findLatestVersion( 998, & v) == 0 );
		ASSERT_TRUE( isDataEqual( v, "v998_sp1" ) ); 

		ASSERT_TRUE( dataDesc_->findLatestVersion( 999, & v) == 0 );
		ASSERT_TRUE( isDataEqual( v, "v999_sp2" ) ); 
	}
}

// Test removing a version and adding a new version with greater data size.
// This happens when updating the data with a greater length.
TEST_F(ClassDataDescTest, DISABLED_Update ) {
	stgDataVersion * v;

	const char * THE_LONG_STRING = "v997_new_longer_longer_longer_data_data_data_1234567890_1234567890_1234567890_1234567890_1234567890_1234567890_1234567890";
	// Add v997_sp0
	ASSERT_TRUE( addUncomittedVersion("v997_sp0", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp0" ) ); 

	// Add v997_sp1
	ASSERT_TRUE( addUncomittedVersion("v997_sp1", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp1" ) ); 

	// Remove latest version
	v = dataDesc_->removeUncomittedVersion();
	ASSERT_TRUE( v != NULL );
	ASSERT_TRUE( freeVersion( v ) == 0 );

	// Add the version with the same savepoint ID.
	ASSERT_TRUE( addUncomittedVersion( THE_LONG_STRING , 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), THE_LONG_STRING ) ); 
	
	// Commit
	ASSERT_TRUE( dataDesc_->commitChanges(997) );

	// Check the comitted changes.
	ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, THE_LONG_STRING ) ); 
}


// Test updating a version in-place. Can do this only when the dataVersion has enough space.
TEST_F(ClassDataDescTest, DISABLED_InPlaceUpdate ) {
	stgDataVersion * v;

	// Add v997_sp0
	ASSERT_TRUE( addUncomittedVersion("v997_sp0", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp0" ) ); 

	// Add v997_sp1
	ASSERT_TRUE( addUncomittedVersion("v997_sp1", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp1" ) ); 

	// Update the version 
	ASSERT_TRUE( dataDesc_->updateUncomittedVersion( 1 /*savepoint*/, stgData("v997_new", strlen("v997_new"))) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_new" ) ); 
	
	// Commit
	ASSERT_TRUE( dataDesc_->commitChanges(997) );

	// Check the that there is no record.
	ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v997_new" ) ); 
}


// Test marking a version deleted.
TEST_F(ClassDataDescTest, DISABLED_Delete ) {
	stgDataVersion * v;

	// Add v997_sp0
	ASSERT_TRUE( addUncomittedVersion("v997_sp0", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp0" ) ); 

	// Add v997_sp1
	ASSERT_TRUE( addUncomittedVersion("v997_sp1", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp1" ) ); 
	
	// Executed when "delete from ... where key='997_sp1'" happens
	// Mark the latest uncomitted version(v997_sp1) deleted.
	ASSERT_TRUE( dataDesc_->setUncomittedVersionDeleted( 1 /*savepoint*/, true/*deleted*/ ) == 0);
	
	// Commit
	ASSERT_TRUE( dataDesc_->commitChanges(997) );

	// Check the that there is no record.
	ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
	ASSERT_TRUE( v == NULL ); 

}


// Test marking a version deleted and then inserting the same key with different value.
TEST_F(ClassDataDescTest, DISABLED_Delete_And_Insert ) {
	stgDataVersion * v;
	
	// Add v997_sp0
	ASSERT_TRUE( addUncomittedVersion("v997_sp0", 0 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp0" ) ); 

	// Add v997_sp1
	ASSERT_TRUE( addUncomittedVersion("v997_sp1", 1 /* savepoint id */) == 0 );
	ASSERT_TRUE( isDataEqual( dataDesc_->getComittedVersion(), "v997_sp1" ) ); 
	
	// Executed when "delete from ... where key='997_sp1'" happens
	// Mark the latest uncomitted version(v997_sp1) deleted.
	ASSERT_TRUE( dataDesc_->setUncomittedVersionDeleted( 1 /*savepoint*/, true/*deleted*/ ) == 0);
	
	// Executed when "insert into ... values ('v997_new')" happens
	ASSERT_TRUE( dataDesc_->setUncomittedVersionDeleted( 1 /*savepoint*/, false/*deleted*/ ) == 0);
	ASSERT_TRUE( dataDesc_->updateUncomittedVersion( 1 /*savepoint*/, stgData("v997_new", strlen("v997_new"))) == 0 );

	// Commit
	ASSERT_TRUE( dataDesc_->commitChanges(997) );

	// Check the new version
	ASSERT_TRUE( dataDesc_->findLatestVersion( 997, & v) == 0 );
	ASSERT_TRUE( isDataEqual( v, "v997_new" ) ); 

}

TEST_F(ClassDataDescTest, DISABLED_TryModification) {
	void * PSEUDO_TRANS1 = (void*) 0x0403;
	void * PSEUDO_TRANS2 = (void*) 0x0622;
	bool success;
	
	stgDataVersion * data1;
	ASSERT_TRUE( allocVersion("data1", & data1 ) == 0 );
	
	ASSERT_TRUE( dataDesc_->isModifyingTransaction( NULL ) );
	// success : TRANS1 tries modification
	{
		ASSERT_FALSE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS1 ) );
		ASSERT_TRUE( dataDesc_->tryModification( PSEUDO_TRANS1, & success ) == 0);
		ASSERT_TRUE( success );
		ASSERT_TRUE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS1 ) );
	}
	// success : TRANS1 tries modification again
	{
		ASSERT_TRUE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS1 ) );
		ASSERT_TRUE( dataDesc_->tryModification( PSEUDO_TRANS1, & success ) == 0);
		ASSERT_TRUE( success );
		ASSERT_TRUE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS1 ) );
	}
	// failure : TRANS2 tries modification
	{
		ASSERT_TRUE( dataDesc_->tryModification( PSEUDO_TRANS2, & success ) == 0);
		ASSERT_FALSE( success );
		ASSERT_FALSE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS2 ) );
	}
	
	// success : TRANS1 ends modification
	{
		ASSERT_TRUE( dataDesc_->endModification( PSEUDO_TRANS1 ) == 0);
		ASSERT_FALSE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS1 ) );
	}
	
	// success : TRANS2 tries modification
	{
		ASSERT_FALSE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS2 ) );
		ASSERT_TRUE( dataDesc_->tryModification( PSEUDO_TRANS2, & success ) == 0);
		ASSERT_TRUE( success );
		ASSERT_TRUE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS2 ) );
	}
	
	// success : TRANS2 ends modification
	{
		ASSERT_TRUE( dataDesc_->endModification( PSEUDO_TRANS2 ) == 0);
		ASSERT_FALSE( dataDesc_->isModifyingTransaction( PSEUDO_TRANS2 ) );
	}
}

TEST_F(ClassDataDescTest, DISABLED_TryModification_MultiThread) {
}